
(function ( exports ) {
    //region Retrieve

    /**
     * Retrieve message
     * @param {string} logicalName Entity Logical Name
     * @param {string} id Entity Unique Identifier
     * @param {string[]} columnSet Requested columns
     * @param {string} requestId Unique Request Identifier
     * @prop {string} logicalName Entity Logical Name
     * @prop {string} id Entity Unique Identifier
     * @prop {string[]} columnSet Requested columns
     * @prop {string} requestId Unique Request Identifier
     * @memberOf COSA.Request
     * @constructor Retrieve
     */
    function Retrieve ( logicalName, id, columnSet, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);
        this.logicalName = logicalName;
        this.id = id;
        this.columnSet = columnSet;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, Retrieve, {
        requestName:COSA.Const.Message.Retrieve,
        validate: function () {
            (new COSA.ValueChecker(this.logicalName))
                .defined()
                .typeOf('string');

            (new COSA.ValueChecker(this.id))
                .defined()
                .guid();

            (new COSA.ValueChecker(this.columnSet))
                .defined()
                .instanceOf(Array)
                .valid();
        },
        retrieveParameters: function () {
            this.parameters = {
                'Target': new COSA.EntityReference(this.logicalName, this.id),
                'ColumnSet': new COSA.ColumnSet(this.columnSet)
            };
        },
        getResponseInstance: function () {
            return new COSA.Response.Retrieve();
        }
    });

    //endregion Retrieve

    //region Create

    /**
     * Create message
     * @param {BusinessEntity} entity Entity to create
     * @param {string} requestId Request unique identifier
     * @prop {BusinessEntity} entity Entity to create
     * @memberOf COSA.Request
     * @constructor Create
     */
    function Create ( entity, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);
        this.entity = entity;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, Create, {
        requestName:COSA.Const.Message.Create,
        validate:function () {
            (new COSA.ValueChecker(this.entity))
                .defined()
                .instanceOf(COSA.BusinessEntity)
                .valid();
        },
        retrieveParameters:function () {
            this.parameters = {
                'Target': this.entity
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.Create();
        }
    });

    //endregion Create
    
    //region Update
    /**
     * Update message
     * @param {BusinessEntity} entity Entity to update
     * @param {string} requestId Guid-like string as Request unique identifier
     * @prop {BusinessEntity} entity Entity
     * @memberOf COSA.Request
     * @constructor Update
     */
    function Update ( entity, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);
        this.entity = entity;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, Update, {
        requestName:COSA.Const.Message.Update,
        validate:function () {
            (new COSA.ValueChecker(this.entity))
                .defined()
                .instanceOf(COSA.BusinessEntity)
                .valid();
        },
        retrieveParameters:function () {
            this.parameters = {
                'Target': this.entity
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.Update();
        }
    });

    //endregion Update

    //region Delete

    /**
     * Delete message
     * @param {string} logicalName Entity logical name
     * @param {string} id Guid-like string as Entity unique identifier
     * @param {string} requestId Guid-like string as Request unique identifier
     * @prop {string} logicalName Entity logical name
     * @prop {string} id Guid-like string as Entity unique identifier
     * @prop {string} requestId Guid-like string as Request unique identifier
     * @example
     *    (new COSA.Request.Delete('contact', 'F396628A-2926-E511-9430-005056B8429A')).execute(function ( r ) {
     *        //Working with a response
     *    });
     * @memberOf COSA.Request
     * @constructor Delete
     */
    function Delete ( logicalName, id, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);
        this.id = id;
        this.logicalName = logicalName;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, Delete, {
        requestName:COSA.Const.Message.Delete,
        validate:function () {
            (new COSA.ValueChecker(this.id))
                .defined()
                .guid();

            (new COSA.ValueChecker(this.logicalName))
                .defined()
                .typeOf('string');
        },
        retrieveParameters:function () {
            this.parameters = {
                'Target': new COSA.EntityReference(this.logicalName, this.id)
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.Delete();
        }
    });

    //endregion Delete

    //region ExecuteMultiple

    /**
     * ExecuteMultiple message
     * @param {OrganizationRequestCollection} requests Requests collection
     * @param {ExecuteMultipleSettings} settings
     * @param {string} requestId Request unique identifier (GUID)
     * @memberOf COSA.Request
     * @example
     * var be_0 = new COSA.BusinessEntity("contact", null, { firstname: 'Michael', lastname: 'Jackson', primaryemail:'mjackson@mail.com'}),
     *     createRq = new COSA.Request.Create(be_0),
     *     deleteRq = new COSA.Request.Delete('contact', 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX'),
     *     execMultRqs = new COSA.OrganizationRequestCollection([createRq, deleteRq]),
     *     execMultSett = new COSA.ExecuteMultipleSettings(true, false),
     *     execMultRq = new COSA.Request.ExecuteMultiple(execMultRqs, execMultSett);
     *
     * execMultRq.execute(callbackFunc);
     * //or
     * COSA.Core.Execute(execMultRq, callbackFunc);
     * @prop {OrganizationRequestCollection} requests
     * @prop {ExecuteMultipleSettings} settings
     * @constructor ExecuteMultiple
     */
    function ExecuteMultiple ( requests, settings, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.requests = requests;
        this.settings = settings;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, ExecuteMultiple, {
        requestName:COSA.Const.Message.ExecuteMultiple,
        validate:function () {
            (new COSA.ValueChecker(this.requests))
                .defined()
                .instanceOf(COSA.OrganizationRequestCollection)
                .valid();

            (new COSA.ValueChecker(this.settings))
                .defined()
                .instanceOf(COSA.ExecuteMultipleSettings)
                .valid();
        },
        retrieveParameters:function () {
            this.parameters = {
                'Requests': this.requests,
                'Settings': this.settings
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.ExecuteMultiple(this.requests);
        }
    });

    //endregion ExecuteMultiple

    //region RetrieveMultiple

    /**
     * RetrieveMultiple message
     * @param {COSA.QueryExpression} queryExpression Query Expression object
     * @param {string} requestId Request unique identifier (GUID)
     * @prop {COSA.QueryExpression} queryExpression Query Expression object
     * @memberOf COSA.Request
     * @constructor RetrieveMultiple
     */
    function RetrieveMultiple ( queryExpression, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.queryExpression = queryExpression;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, RetrieveMultiple, {
        requestName:COSA.Const.Message.RetrieveMultiple,
        validate:function () {
            (new COSA.ValueChecker(this.queryExpression))
                .defined()
                .instanceOf(COSA.QueryExpression)
                .valid();
        },
        retrieveParameters:function () {
            this.parameters = {
                'Query': this.queryExpression
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.RetrieveMultiple();
        }
    });

    //endregion RetrieveMultiple

    //region RetrieveAttribute

    /**
     * RetrieveAttribute message
     * @param {string} entityLogicalName Entity Logical Name
     * @param {string} attrLogicalName Attribute LogicalName
     * @param {boolean} retrieveAsIfPublished Retrieve As If Published
     * @param {string} requestId Request unique identifier (GUID)
     * @prop {string} entityLogicalName Entity Logical Name
     * @prop {string} attributeLogicalName Attribute Logical Name
     * @prop {boolean} retrieveAsIfPublished Retrieve As If Published
     * @memberOf COSA.Request
     * @constructor RetrieveAttribute
     */
    function RetrieveAttribute ( entityLogicalName, attrLogicalName, retrieveAsIfPublished, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.entityLogicalName = entityLogicalName;
        this.attributeLogicalName = attrLogicalName;
        this.retrieveAsIfPublished = retrieveAsIfPublished;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, RetrieveAttribute, {
        requestName:COSA.Const.Message.RetrieveAttribute,
        retrieveParameters:function () {
            this.parameters = {
                'MetadataId': new COSA.XrmGuid('00000000-0000-0000-0000-000000000000'),
                'RetrieveAsIfPublished': new COSA.XrmBoolean(this.retrieveAsIfPublished),
                'EntityLogicalName': new COSA.XrmString(this.entityLogicalName),
                'LogicalName': new COSA.XrmString(this.attributeLogicalName)
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.RetrieveAttribute();
        }
    });

    //endregion RetrieveAttribute

    //region RetrieveEntity

    /**
     * RetrieveEntity message
     * @param {string} logicalName Entity logical name
     * @param {COSA.EntityFilters} filters Entity filters
     * @param {boolean} retrieveAsIfPublished Retrieve As If Published
     * @param {string} requestId Request unique identifier (GUID)
     * @prop {string} logicalName Entity Logical Name
     * @prop {COSA.EntityFilters} entityFilters EntityFilters
     * @prop {boolean} retrieveAsIfPublished Retrieve As If Published
     * @memberOf COSA.Request
     * @constructor RetrieveEntity
     */
    function RetrieveEntity ( logicalName, filters, retrieveAsIfPublished, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.logicalName = logicalName;
        this.entityFilters = filters;
        this.retrieveAsIfPublished = retrieveAsIfPublished;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, RetrieveEntity, {
        requestName:COSA.Const.Message.RetrieveEntity,
        retrieveParameters:function () {
            this.parameters = {
                'EntityFilters': new COSA.EntityFilters(this.entityFilters),
                'MetadataId': new COSA.XrmGuid('00000000-0000-0000-0000-000000000000'),
                'LogicalName': new COSA.XrmString(this.logicalName),
                'RetrieveAsIfPublished': new COSA.XrmBoolean(this.retrieveAsIfPublished)
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.RetrieveEntity();
        }
    });

    //endregion RetrieveEntity

    //region RetrieveAllEntities

    /**
     * RetrieveAllEntities message
     * @param {COSA.EntityFilters} filters Entity Filters
     * @param {boolean} retrieveAsIfPublished Retrieve As If Published
     * @param {string} requestId Request unique identifier (GUID)
     * @prop {COSA.EntityFilters} entityFilters EntityFilters
     * @prop {boolean} retrieveAsIfPublished Retrieve As If Published
     * @memberOf COSA.Request
     * @constructor RetrieveAllEntities
     */
    function RetrieveAllEntities ( filters, retrieveAsIfPublished, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.entityFilters = filters;
        this.retrieveAsIfPublished = retrieveAsIfPublished;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, RetrieveAllEntities, {
        requestName:COSA.Const.Message.RetrieveAllEntities,
        retrieveParameters:function () {
            this.parameters = {
                'EntityFilters': new COSA.EntityFilters(this.entityFilters),
                'RetrieveAsIfPublished': new COSA.XrmBoolean(this.retrieveAsIfPublished)
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.RetrieveAllEntities();
        }
    });

    //endregion RetrieveAllEntities

    //region Associate

    /**
     * Associate message
     * @param {string} relationshipSchemaName Relationship Schema Name
     * @param {COSA.EntityReference} target Target
     * @param {COSA.EntityReferenceCollection} relatedEntities Related Entities Collection
     * @param {string} requestId Request unique identifier (GUID)
     * @prop {string} relationshipSchemaName Relationship Schema Name
     * @prop {COSA.EntityReference} target Target
     * @prop {COSA.EntityReferenceCollection} relatedEntities Related Entities
     * @memberOf COSA.Request
     * @constructor Associate
     */
    function Associate ( relationshipSchemaName, target, relatedEntities, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.relationshipSchemaName = relationshipSchemaName;
        this.target = target;
        this.relatedEntities = relatedEntities;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, Associate, {
        requestName:COSA.Const.Message.Associate,
        retrieveParameters:function () {
            this.parameters = {
                'Target': this.target,
                'Relationship': new COSA.Relationship(this.relationshipSchemaName),
                'RelatedEntities': new COSA.EntityReferenceCollection(this.relatedEntities)
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.Associate();
        }
    });

    //endregion Associate

    //region Disassociate

    /**
     * Disassociate message
     * @param {string} relationshipSchemaName RelationshipSchemaName
     * @param {COSA.EntityReference} target Target
     * @param {COSA.EntityReferenceCollection} relatedEntities Related Entities
     * @param {string} requestId Request unique identifier (GUID)
     * @prop {string} relationshipSchemaName RelationshipSchemaName
     * @prop {COSA.EntityReference} target Target
     * @prop {COSA.EntityReferenceCollection} relatedEntities Related Entities
     * @memberOf COSA.Request
     * @constructor Disassociate
     */
    function Disassociate ( relationshipSchemaName, target, relatedEntities, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.relationshipSchemaName = relationshipSchemaName;
        this.target = target;
        this.relatedEntities = relatedEntities;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, Disassociate, {
        requestName:COSA.Const.Message.Disassociate,
        retrieveParameters:function () {
            this.parameters = {
                'Target': this.target,
                'Relationship': new COSA.Relationship(this.relationshipSchemaName),
                'RelatedEntities': new COSA.EntityReferenceCollection(this.relatedEntities)
            };
        },
        getResponseInstance:function () {
            return new COSA.Response.Disassociate();
        }
    });

    //endregion Disassociate

    //region RetrieveMetadataChanges

    /**
     * RetrieveMetadataChanges message
     * @param {COSA.MetadataFilterExpression} query Query
     * @param {string} clientVersionStamp Client Version Stamp
     * @param {COSA.DeletedMetadataFilters} deletedMetadataFilters Deleted Metadata Filters
     * @param requestId
     * @prop {COSA.MetadataFilterExpression} query Query
     * @prop {string} clientVersionStamp Client Version Stamp
     * @prop {COSA.DeletedMetadataFilters} deletedMetadataFilters Deleted Metadata Filters
     * @memberOf COSA.Request
     * @constructor RetrieveMetadataChanges
     */
    function RetrieveMetadataChanges ( query, clientVersionStamp, deletedMetadataFilters, requestId ) {
        COSA.Request.XrmRequest.call(this, requestId);

        this.query = query;
        this.clientVersionStamp = clientVersionStamp;
        this.deletedMetadataFilters = deletedMetadataFilters;
    }

    COSA.Util.deriveOverride(COSA.Request.XrmRequest, RetrieveMetadataChanges, {
        requestName:COSA.Const.Message.RetrieveMetadataChanges,
        retrieveParameters:function () {
            this.parameters = {
                'Query': this.query
            };

            if ( typeof this.deletedMetadataFilters != 'undefined' ) {
                this.parameters.DeletedMetadataFilters = this.deletedMetadataFilters;
            }

            if ( typeof this.clientVersionStamp != 'undefined' ) {
                this.parameters.ClientVersionStamp = new COSA.XrmString(this.clientVersionStamp);
            }
        },
        getResponseInstance: function () {
            return new COSA.Response.RetrieveMetadataChanges();
        }
    });

    //endregion RetrieveMetadataChanges

    exports.Retrieve = Retrieve;
    exports.RetrieveMultiple = RetrieveMultiple;
    exports.Create = Create;
    exports.Update = Update;
    exports.Delete = Delete;
    exports.ExecuteMultiple = ExecuteMultiple;
    exports.RetrieveAttribute = RetrieveAttribute;
    exports.RetrieveEntity = RetrieveEntity;
    exports.RetrieveAllEntities = RetrieveAllEntities;
    exports.Associate = Associate;
    exports.Disassociate = Disassociate;
    exports.RetrieveMetadataChanges = RetrieveMetadataChanges;

})(COSA.Request);
